using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

#if WINDOWS
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
#endif

namespace MicroStar
{
    /// <summary>
    /// Defines a single particle effect
    /// </summary>
    public class ParticleEffect
    {
        private static readonly Random Random = new Random();

        private int activeParticles;
        private Particle[] particles;

        /// <summary>
        /// Constructor.  Initializes properties and adds the effect to the Components collection.
        /// </summary>
        public ParticleEffect()
        {
            Emitter = new Emitter();
            ParticleColor = Color.White;

            Textures = new List<string>();
            Modifiers = new List<Modifier>();

            IsDead = true;
        }

        // TODO: Reconsider initialization code

        /// <summary>
        /// Initializes the effect.  This should only be done after all modifiers, forces, emitters and textures have been defined
        /// </summary>
        public void LoadContent(ContentManager content)
        {
            Emitter.Initialize(MaxParticles);
            particles = new Particle[MaxParticles];

            for (int i = 0; i < particles.Length; i++)
            {
                Texture2D texture;

                if (Textures.Count > 0)
                    texture =  content.Load<Texture2D>(Textures[Random.Next(Textures.Count)]);
                else
                    throw new InvalidOperationException("Error initializing particles. At least one texture must be specified");

                particles[i] = new Particle { Lifespan = ParticleLifespan, Texture = texture, Color = ParticleColor };
                Emitter.FreeParticles.Enqueue(particles[i]);
            }
        }

        /// <summary>
        /// Emits particles at a particular position
        /// </summary>
        /// <param name="gameTime">Current GameTime object</param>
        /// <param name="position">Central position to emit particles from</param>
        public void Emit(GameTime gameTime, Vector2 position)
        {
            if (particles == null)
                throw new NullReferenceException("Error emitting particles.  Effect has not been initialized.");

            Emitter.SetAdditionalParameters(ParticleColor, ParticleLifespan);
            Emitter.Emit(gameTime.TotalGameTime.TotalMilliseconds, position);
        }

        /// <summary>
        /// Update particles and apply modifiers
        /// </summary>
        /// <param name="gameTime">Current GameTime object</param>
        public void Update(GameTime gameTime)
        {
            if (particles == null)
                throw new NullReferenceException("Error updating particle effect.  Effect has not been initialized.");

            for (int i = 0; i < particles.Length; i++)
            {
                if (particles[i].IsAlive)
                {
                    IsDead = false;
                    float particleAge = (float)(gameTime.TotalGameTime.TotalMilliseconds - particles[i].Inception) / particles[i].Lifespan;

                    for (int j = 0; j < Modifiers.Count; j++)
                        Modifiers[j].Update(particleAge, gameTime.TotalGameTime.TotalMilliseconds, gameTime.ElapsedGameTime.TotalSeconds, particles[i]);

                    particles[i].Update(gameTime.TotalGameTime.TotalMilliseconds);
                    activeParticles++;

                    if (!particles[i].IsAlive)
                        Emitter.FreeParticles.Enqueue(particles[i]);
                }
            }

            if (activeParticles == 0)
                IsDead = true;

            if (activeParticles > HighestCount)
                HighestCount = activeParticles;

            activeParticles = 0;
        }

        // TODO: Refactor particle effect updating and drawing code.  Try not to loop through the whole array

        /// <summary>
        /// Draw the complete effect to the screen
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            if (particles == null)
                throw new NullReferenceException("Error drawing particle effect.  Effect has not been initialized.");

            for (int i = 0; i < particles.Length; i++)
            {
                if (particles[i].IsAlive)
                    particles[i].Draw(spriteBatch);
            }
        }

#if WINDOWS
        /// <summary>
        /// Saves the effect as an XML configuration file
        /// </summary>
        /// <param name="savePath">File path to save the file to</param>
        public void Save(string savePath)
        {
            XmlTextWriter xmlTextWriter = new XmlTextWriter(savePath, Encoding.UTF8);
            xmlTextWriter.Formatting = Formatting.Indented;
            IntermediateSerializer.Serialize(xmlTextWriter, this, "ParticleEffect");
            xmlTextWriter.Flush();
        }
#endif

        /// <summary>
        /// Gets or sets the total number of particles allowed for this effect.  Initialize will need to be called for changes to take effect
        /// </summary>
        public int MaxParticles { get; set; }

        /// <summary>
        /// Gets or sets the lifespan of each particle in this effect
        /// </summary>
        public int ParticleLifespan { get; set; }

        /// <summary>
        /// Gets or sets the color of each particle in this effect
        /// </summary>
        public Color ParticleColor { get; set; }

        /// <summary>
        /// Gets or sets the emitter for this effect
        /// </summary>
        public Emitter Emitter { get; set; }

        /// <summary>
        /// Gets or sets the list of particle texture names
        /// </summary>
        public List<string> Textures { get; set; }

        /// <summary>
        /// Gets or sets the list of effect modifiers
        /// </summary>
        public List<Modifier> Modifiers { get; set; }

        /// <summary>
        /// Gets boolean indicating whether all particles in the effect are dead
        /// </summary>
        public bool IsDead { get; private set; }

        /// <summary>
        /// Gets the highest number of particles that have been active at once for this effect
        /// </summary>
        public int HighestCount { get; private set; }
    }
}
